import { jest } from '@jest/globals';
import MultiplayerGameEngine from '../../services/MultiplayerGameEngine.js';
import { mockLogger, testUtils } from '../setup.js';

// 模拟Card和Player模块
jest.unstable_mockModule('../../game/Card.js', () => ({
  createDeck: jest.fn(),
  Card: {
    fromJSON: jest.fn((data) => ({
      id: data.id,
      suit: data.suit,
      rank: data.rank,
      power: data.power,
      displayName: data.displayName,
      canPairWith: jest.fn().mockReturnValue(false),
      toJSON: jest.fn().mockReturnValue(data)
    }))
  }
}));

jest.unstable_mockModule('../../game/Player.js', () => ({
  Player: jest.fn().mockImplementation((id, name, isAI, seatIndex) => ({
    id,
    name,
    isAI,
    seatIndex,
    cards: [],
    getSafeInfo: jest.fn().mockReturnValue({ id, name, seatIndex }),
    toJSON: jest.fn().mockReturnValue({ id, name, isAI, seatIndex, cards: [] })
  }))
}));

const createMockDatabaseService = () => ({
  createGameSession: jest.fn(),
  updateGameSession: jest.fn(),
  getGameSession: jest.fn(),
  recordGameAction: jest.fn()
});

describe('MultiplayerGameEngine', () => {
  let gameEngine;
  let mockDb;
  let mockDeck;

  beforeEach(async () => {
    // 重新导入模块以获取mock
    const { createDeck } = await import('../../game/Card.js');
    
    mockDb = createMockDatabaseService();
    gameEngine = new MultiplayerGameEngine(mockDb);
    
    // 设置模拟数据
    mockDeck = testUtils.createTestCards(24);
    createDeck.mockReturnValue(mockDeck);
    
    // 模拟logger
    gameEngine.logger = mockLogger;
  });

  afterEach(() => {
    jest.clearAllMocks();
    // 清理内存状态
    gameEngine.activeSessions.clear();
    gameEngine.roomSessions.clear();
  });

  describe('startGame', () => {
    const mockPlayers = [
      { id: 'player-1', name: 'Player1', seatIndex: 0 },
      { id: 'player-2', name: 'Player2', seatIndex: 1 },
      { id: 'player-3', name: 'Player3', seatIndex: 2 }
    ];

    it('should start game successfully with 3 players', async () => {
      mockDb.createGameSession.mockResolvedValue(true);

      const result = await gameEngine.startGame('TEST01', mockPlayers);

      expect(result.success).toBe(true);
      expect(result.data).toHaveProperty('sessionId');
      expect(result.data).toHaveProperty('gameState');
      
      // 验证会话创建
      expect(mockDb.createGameSession).toHaveBeenCalled();
      expect(gameEngine.activeSessions.size).toBe(1);
      expect(gameEngine.roomSessions.get('TEST01')).toBeDefined();
    });

    it('should reject starting game that already exists', async () => {
      gameEngine.roomSessions.set('TEST01', 'existing-session');

      const result = await gameEngine.startGame('TEST01', mockPlayers);

      expect(result.success).toBe(false);
      expect(result.error.code).toBe('GAME_ALREADY_STARTED');
    });

    it('should reject invalid player count', async () => {
      const result = await gameEngine.startGame('TEST01', [mockPlayers[0]]);

      expect(result.success).toBe(false);
      expect(result.error.code).toBe('INVALID_PLAYERS');
    });

    it('should handle database errors', async () => {
      mockDb.createGameSession.mockRejectedValue(new Error('DB Error'));

      const result = await gameEngine.startGame('TEST01', mockPlayers);

      expect(result.success).toBe(false);
      expect(result.error.code).toBe('SERVER_ERROR');
    });

    it('should handle continuous wuxiang situation', async () => {
      // 模拟连续无相情况
      jest.spyOn(gameEngine, 'checkWuxiang')
          .mockReturnValueOnce({ hasWuxiang: true, player: mockPlayers[0] })
          .mockReturnValueOnce({ hasWuxiang: true, player: mockPlayers[0] });

      const result = await gameEngine.startGame('TEST01', mockPlayers);

      expect(result.success).toBe(false);
      expect(result.error.code).toBe('CONTINUOUS_WUXIANG');
    });
  });

  describe('playCards', () => {
    let session;
    const testCards = [
      { id: 'card-1', suit: 'hearts', rank: '10', power: 20, displayName: '红尔' }
    ];

    beforeEach(() => {
      // 创建测试会话
      session = {
        id: 'test-session',
        roomId: 'TEST01',
        gameStatus: 'playing',
        waitingForCollection: false,
        currentPlayerIndex: 0,
        players: [
          {
            id: 'player-1',
            name: 'Player1',
            cards: testCards.map(c => ({ ...c, canPairWith: jest.fn() }))
          },
          { id: 'player-2', name: 'Player2', cards: [] },
          { id: 'player-3', name: 'Player3', cards: [] }
        ],
        roundCards: [],
        actionSequence: 0
      };
      
      gameEngine.activeSessions.set('test-session', session);
      gameEngine.roomSessions.set('TEST01', 'test-session');
      mockDb.recordGameAction.mockResolvedValue(true);
      mockDb.updateGameSession.mockResolvedValue(true);
    });

    it('should play cards successfully', async () => {
      jest.spyOn(gameEngine, 'validatePlay').mockReturnValue({ valid: true });
      jest.spyOn(gameEngine, 'calculatePlayPower').mockReturnValue(20);

      const result = await gameEngine.playCards('TEST01', 'player-1', testCards);

      expect(result.success).toBe(true);
      expect(session.roundCards).toHaveLength(1);
      expect(session.currentPlayerIndex).toBe(1); // 下一个玩家
      expect(mockDb.recordGameAction).toHaveBeenCalled();
    });

    it('should reject play when not player turn', async () => {
      session.currentPlayerIndex = 1; // 不是player-1的回合

      const result = await gameEngine.playCards('TEST01', 'player-1', testCards);

      expect(result.success).toBe(false);
      expect(result.error.code).toBe('NOT_YOUR_TURN');
    });

    it('should reject play when waiting for collection', async () => {
      session.waitingForCollection = true;

      const result = await gameEngine.playCards('TEST01', 'player-1', testCards);

      expect(result.success).toBe(false);
      expect(result.error.code).toBe('WAITING_COLLECTION');
    });

    it('should reject invalid card play', async () => {
      jest.spyOn(gameEngine, 'validatePlay')
          .mockReturnValue({ valid: false, message: 'Invalid play' });

      const result = await gameEngine.playCards('TEST01', 'player-1', testCards);

      expect(result.success).toBe(false);
      expect(result.error.code).toBe('INVALID_PLAY');
    });

    it('should set waiting for collection after 3 plays', async () => {
      jest.spyOn(gameEngine, 'validatePlay').mockReturnValue({ valid: true });
      jest.spyOn(gameEngine, 'calculatePlayPower').mockReturnValue(20);
      
      // 模拟已有两次出牌
      session.roundCards = [
        { player: session.players[1], cards: [], power: 15 },
        { player: session.players[2], cards: [], power: 18 }
      ];

      const result = await gameEngine.playCards('TEST01', 'player-1', testCards);

      expect(result.success).toBe(true);
      expect(session.waitingForCollection).toBe(true);
      expect(session.roundCards).toHaveLength(3);
    });
  });

  describe('passTurn', () => {
    let session;

    beforeEach(() => {
      session = {
        id: 'test-session',
        roomId: 'TEST01',
        gameStatus: 'playing',
        currentPlayerIndex: 1,
        players: [
          { id: 'player-1', name: 'Player1', cards: [] },
          { id: 'player-2', name: 'Player2', cards: testUtils.createTestCards(3) },
          { id: 'player-3', name: 'Player3', cards: [] }
        ],
        roundCards: [
          { player: { id: 'player-1' }, cards: [{ rank: '10' }], power: 20 }
        ],
        actionSequence: 0
      };
      
      gameEngine.activeSessions.set('test-session', session);
      gameEngine.roomSessions.set('TEST01', 'test-session');
      mockDb.recordGameAction.mockResolvedValue(true);
      mockDb.updateGameSession.mockResolvedValue(true);
    });

    it('should pass turn successfully when no valid plays', async () => {
      jest.spyOn(gameEngine, 'hasValidPlay').mockReturnValue(false);

      const result = await gameEngine.passTurn('TEST01', 'player-2');

      expect(result.success).toBe(true);
      expect(session.roundCards).toHaveLength(2);
      expect(session.roundCards[1].passed).toBe(true);
      expect(session.currentPlayerIndex).toBe(2); // 下一个玩家
      expect(session.roundCards[1].discardedCards).toHaveLength(1);
    });

    it('should reject pass when can play', async () => {
      jest.spyOn(gameEngine, 'hasValidPlay').mockReturnValue(true);

      const result = await gameEngine.passTurn('TEST01', 'player-2');

      expect(result.success).toBe(false);
      expect(result.error.code).toBe('MUST_PLAY');
    });

    it('should reject pass on first play', async () => {
      session.roundCards = [];

      const result = await gameEngine.passTurn('TEST01', 'player-2');

      expect(result.success).toBe(false);
      expect(result.error.code).toBe('CANNOT_PASS_FIRST');
    });

    it('should auto complete discard cards to match required count', async () => {
      jest.spyOn(gameEngine, 'hasValidPlay').mockReturnValue(false);
      session.roundCards[0].cards = [{ id: 'lead-card-1' }, { id: 'lead-card-2' }];
      session.players[1].cards = testUtils.createTestCards(4);

      const manualSelection = [session.players[1].cards[0]];

      const result = await gameEngine.passTurn('TEST01', 'player-2', manualSelection);

      expect(result.success).toBe(true);
      const passPlay = session.roundCards[1];
      expect(passPlay.discardedCards).toHaveLength(2);
    });

    it('should reject pass when discard count exceeds required', async () => {
      jest.spyOn(gameEngine, 'hasValidPlay').mockReturnValue(false);
      session.roundCards[0].cards = [{ id: 'lead-card-1' }];
      session.players[1].cards = testUtils.createTestCards(4);

      const excessiveSelection = session.players[1].cards.slice(0, 2);

      const result = await gameEngine.passTurn('TEST01', 'player-2', excessiveSelection);

      expect(result.success).toBe(false);
      expect(result.error.code).toBe('INVALID_PASS_COUNT');
    });
  });

  describe('collectCards', () => {
    let session;

    beforeEach(() => {
      session = {
        id: 'test-session',
        roomId: 'TEST01',
        waitingForCollection: true,
        currentRound: 1,
        roundCards: [
          { player: { id: 'player-1' }, cards: [{}], power: 15 },
          { player: { id: 'player-2' }, cards: [{}], power: 20, passed: false },
          { player: { id: 'player-3' }, cards: [{}], power: 18 }
        ],
        collectedCards: { 'player-1': 0, 'player-2': 0, 'player-3': 0 },
        players: [
          { id: 'player-1', cards: [] },
          { id: 'player-2', cards: [] },
          { id: 'player-3', cards: [] }
        ],
        actionSequence: 0
      };
      
      gameEngine.activeSessions.set('test-session', session);
      gameEngine.roomSessions.set('TEST01', 'test-session');
      mockDb.recordGameAction.mockResolvedValue(true);
      mockDb.updateGameSession.mockResolvedValue(true);
    });

    it('should collect cards successfully', async () => {
      jest.spyOn(gameEngine, 'checkGameOver').mockReturnValue(false);

      const result = await gameEngine.collectCards('TEST01');

      expect(result.success).toBe(true);
      expect(session.waitingForCollection).toBe(false);
      expect(session.currentRound).toBe(2);
      expect(session.roundCards).toHaveLength(0);
      
      // 验证收牌数量更新
      expect(session.collectedCards['player-2']).toBe(3); // 赢家收3张牌
      expect(mockDb.recordGameAction).toHaveBeenCalledWith(expect.objectContaining({
        playerId: 'player-2',
        actionType: 'collect'
      }));
    });

    it('should reject when not in collection phase', async () => {
      session.waitingForCollection = false;

      const result = await gameEngine.collectCards('TEST01');

      expect(result.success).toBe(false);
      expect(result.error.code).toBe('NOT_COLLECTING');
    });

    it('should end game when player has no cards', async () => {
      jest.spyOn(gameEngine, 'checkGameOver').mockReturnValue(true);
      jest.spyOn(gameEngine, 'endGame').mockResolvedValue(true);

      const result = await gameEngine.collectCards('TEST01');

      expect(result.success).toBe(true);
      expect(gameEngine.endGame).toHaveBeenCalled();
    });
  });

  describe('initiateKouLe', () => {
    let session;

    beforeEach(() => {
      session = {
        id: 'test-session',
        roomId: 'TEST01',
        currentPlayerIndex: 0,
        players: [{ id: 'player-1', name: 'Player1' }],
        kouLeStatus: { isActive: false },
        actionSequence: 0
      };
      
      gameEngine.activeSessions.set('test-session', session);
      gameEngine.roomSessions.set('TEST01', 'test-session');
      mockDb.updateGameSession.mockResolvedValue(true);
    });

    it('should initiate koule successfully', async () => {
      const result = await gameEngine.initiateKouLe('TEST01', 'player-1');

      expect(result.success).toBe(true);
      expect(session.kouLeStatus.isActive).toBe(true);
      expect(session.kouLeStatus.initiator).toBe('player-1');
      expect(session.kouLeStatus.decisions['player-1']).toBe('kou');
    });

    it('should reject when not player turn', async () => {
      session.currentPlayerIndex = 1;

      const result = await gameEngine.initiateKouLe('TEST01', 'player-1');

      expect(result.success).toBe(false);
      expect(result.error.code).toBe('NOT_YOUR_TURN');
    });

    it('should reject when koule already active', async () => {
      session.kouLeStatus.isActive = true;

      const result = await gameEngine.initiateKouLe('TEST01', 'player-1');

      expect(result.success).toBe(false);
      expect(result.error.code).toBe('KOULE_ACTIVE');
    });
  });

  describe('respondToKouLe', () => {
    let session;

    beforeEach(() => {
      session = {
        id: 'test-session',
        roomId: 'TEST01',
        players: [
          { id: 'player-1', name: 'Player1' },
          { id: 'player-2', name: 'Player2' },
          { id: 'player-3', name: 'Player3' }
        ],
        kouLeStatus: {
          isActive: true,
          initiator: 'player-1',
          decisions: { 'player-1': 'kou' },
          xuanPlayers: []
        },
        actionSequence: 0
      };
      
      gameEngine.activeSessions.set('test-session', session);
      gameEngine.roomSessions.set('TEST01', 'test-session');
      mockDb.updateGameSession.mockResolvedValue(true);
    });

    it('should respond with kou successfully', async () => {
      const result = await gameEngine.respondToKouLe('TEST01', 'player-2', 'kou');

      expect(result.success).toBe(true);
      expect(session.kouLeStatus.decisions['player-2']).toBe('kou');
    });

    it('should respond with xuan and resolve immediately', async () => {
      jest.spyOn(gameEngine, 'resolveKouLe')
          .mockReturnValue({ type: 'continue', message: 'Game continues' });

      const result = await gameEngine.respondToKouLe('TEST01', 'player-2', 'xuan');

      expect(result.success).toBe(true);
      expect(session.kouLeStatus.xuanPlayers).toContain('player-2');
      expect(result.data.kouLeResult).toBeDefined();
    });

    it('should reject when koule not active', async () => {
      session.kouLeStatus.isActive = false;

      const result = await gameEngine.respondToKouLe('TEST01', 'player-2', 'kou');

      expect(result.success).toBe(false);
      expect(result.error.code).toBe('KOULE_INACTIVE');
    });

    it('should reject initiator responding again', async () => {
      const result = await gameEngine.respondToKouLe('TEST01', 'player-1', 'kou');

      expect(result.success).toBe(false);
      expect(result.error.code).toBe('ALREADY_DECIDED');
    });
  });

  describe('validatePlay', () => {
    let session, player;

    beforeEach(() => {
      player = {
        cards: [
          { id: 'card-1', rank: '10', canPairWith: jest.fn().mockReturnValue(false) },
          { id: 'card-2', rank: 'J', canPairWith: jest.fn().mockReturnValue(true) }
        ]
      };
      
      session = {
        roundCards: []
      };
    });

    it('should validate single card play', () => {
      const cards = [player.cards[0]];
      
      const result = gameEngine.validatePlay(session, player, cards);

      expect(result.valid).toBe(true);
    });

    it('should reject empty card array', () => {
      const result = gameEngine.validatePlay(session, player, []);

      expect(result.valid).toBe(false);
      expect(result.message).toContain('至少要出一张牌');
    });

    it('should reject cards not owned by player', () => {
      const cards = [{ id: 'not-owned', rank: 'K' }];
      
      const result = gameEngine.validatePlay(session, player, cards);

      expect(result.valid).toBe(false);
      expect(result.message).toContain('您没有这些牌');
    });

    it('should validate valid pair', () => {
      const cards = [player.cards[0], player.cards[1]];
      player.cards[0].canPairWith.mockReturnValue(true);
      
      const result = gameEngine.validatePlay(session, player, cards);

      expect(result.valid).toBe(true);
    });

    it('should reject invalid pair', () => {
      const cards = [player.cards[0], player.cards[1]];
      player.cards[0].canPairWith.mockReturnValue(false);
      
      const result = gameEngine.validatePlay(session, player, cards);

      expect(result.valid).toBe(false);
      expect(result.message).toContain('不能组成对子');
    });
  });

  describe('calculatePlayPower', () => {
    it('should calculate single card power', () => {
      const cards = [{ power: 15 }];
      
      const power = gameEngine.calculatePlayPower(cards);

      expect(power).toBe(15);
    });

    it('should calculate pair power', () => {
      const cards = [{ power: 15 }, { power: 15 }];
      
      const power = gameEngine.calculatePlayPower(cards);

      expect(power).toBe(115); // 15 + 100
    });

    it('should handle special pairs (大小王对)', () => {
      const cards = [
        { rank: 'joker', displayName: '大王' },
        { rank: 'joker', displayName: '小王' }
      ];
      
      const power = gameEngine.calculatePlayPower(cards);

      expect(power).toBe(125);
    });

    it('should handle red er pair', () => {
      const cards = [
        { displayName: '红尔' },
        { displayName: '红尔' }
      ];
      
      const power = gameEngine.calculatePlayPower(cards);

      expect(power).toBe(125);
    });

    it('should calculate multiple cards power', () => {
      const cards = [
        { power: 15 },
        { power: 16 },
        { power: 14 }
      ];
      
      const power = gameEngine.calculatePlayPower(cards);

      expect(power).toBe(216); // max(15,16,14) + 200
    });
  });

  describe('checkWuxiang', () => {
    it('should detect wuxiang when player has no key cards', () => {
      const session = {
        players: [{
          cards: [
            { displayName: '红卒' },
            { displayName: '黑卒' },
            { displayName: '红曲曲' }
          ]
        }]
      };

      const result = gameEngine.checkWuxiang(session);

      expect(result.hasWuxiang).toBe(true);
      expect(result.player).toBe(session.players[0]);
    });

    it('should not detect wuxiang when player has key cards', () => {
      const session = {
        players: [{
          cards: [
            { displayName: '红相' },
            { displayName: '黑卒' },
            { displayName: '红曲曲' }
          ]
        }]
      };

      const result = gameEngine.checkWuxiang(session);

      expect(result.hasWuxiang).toBe(false);
    });
  });

  describe('calculateGameResults', () => {
    it('should calculate correct results for different collection levels', () => {
      const session = {
        players: [
          { getSafeInfo: () => ({ id: 'p1', name: 'Player1' }) },
          { getSafeInfo: () => ({ id: 'p2', name: 'Player2' }) },
          { getSafeInfo: () => ({ id: 'p3', name: 'Player3' }) }
        ],
        collectedCards: {
          'p1': 8,  // 不够
          'p2': 15, // 五了
          'p3': 20  // 此了
        }
      };

      const results = gameEngine.calculateGameResults(session);

      expect(results.results).toHaveLength(3);
      expect(results.results[0].level).toBe('不够');
      expect(results.results[0].coins).toBe(0);
      expect(results.results[1].level).toBe('五了');
      expect(results.results[1].coins).toBe(2);
      expect(results.results[2].level).toBe('此了');
      expect(results.results[2].coins).toBe(3);
    });
  });

  describe('cleanup', () => {
    it('should cleanup expired sessions', () => {
      // 设置一个过期的已完成会话
      const expiredSession = {
        gameStatus: 'finished',
        startedAt: new Date(Date.now() - 3 * 60 * 60 * 1000), // 3小时前
        roomId: 'EXPIRED01'
      };
      
      gameEngine.activeSessions.set('expired-session', expiredSession);
      gameEngine.roomSessions.set('EXPIRED01', 'expired-session');

      gameEngine.cleanup();

      expect(gameEngine.activeSessions.has('expired-session')).toBe(false);
      expect(gameEngine.roomSessions.has('EXPIRED01')).toBe(false);
    });

    it('should not cleanup active sessions', () => {
      const activeSession = {
        gameStatus: 'playing',
        startedAt: new Date(), // 刚创建
        roomId: 'ACTIVE01'
      };
      
      gameEngine.activeSessions.set('active-session', activeSession);
      gameEngine.roomSessions.set('ACTIVE01', 'active-session');

      gameEngine.cleanup();

      expect(gameEngine.activeSessions.has('active-session')).toBe(true);
      expect(gameEngine.roomSessions.has('ACTIVE01')).toBe(true);
    });
  });

  describe('getStats', () => {
    it('should return correct statistics', () => {
      gameEngine.activeSessions.set('session1', { gameStatus: 'playing' });
      gameEngine.activeSessions.set('session2', { gameStatus: 'finished' });
      gameEngine.activeSessions.set('session3', { gameStatus: 'playing' });

      const stats = gameEngine.getStats();

      expect(stats).toEqual({
        activeSessions: 3,
        playingSessions: 2,
        finishedSessions: 1
      });
    });
  });
});
